package nancal.mp.mvc.devops


import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import nancal.mp.db.mongo.entity.dev.DbConnection
import nancal.mp.db.mongo.mor
import nancal.mp.service.EsCrudService
import nancal.mp.service.MongoCrudService
import nancal.mp.service.MySqlCrudService
import nbcp.base.annotation.Require
import nbcp.base.comm.ApiResult
import nbcp.base.comm.JsonMap
import nbcp.base.comm.ListResult
import nbcp.base.comm.const
import nbcp.base.db.IdName
import nbcp.base.db.IdNamePath
import nbcp.base.enums.JsonStyleScopeEnum
import nbcp.base.extend.*
import nbcp.base.model.BaseIdEntity
import nbcp.base.utils.CodeUtil
import nbcp.base.utils.FileUtil
import nbcp.base.utils.StringUtil
import nbcp.base.utils.ZipUtil
import nbcp.mvc.sys.parentAlert
import nbcp.mvc.sys.setDownloadFileName
import nbcp.myoql.code.generator.db.mysql.EntityDbItemData
import nbcp.myoql.code.generator.db.mysql.SqlDbEntityGenerator
import nbcp.myoql.code.generator.db.mysql.model.NameCodeInfo
import nbcp.myoql.code.generator.db.mysql.model.TableIndexMetaData
import nbcp.myoql.code.generator.tool.CrudCodeGeneratorUtil
import nbcp.myoql.code.generator.tool.Enum4JsGenerator
import nbcp.myoql.db.comm.BaseMetaData
import nbcp.myoql.db.enums.DatabaseEnum
import nbcp.myoql.db.mongo.component.MongoBaseMetaCollection
import nbcp.myoql.db.mongo.queryById
import nbcp.myoql.db.sql.DataSourceScope
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import java.io.File
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse

@RequestMapping("/dev")
@RestController
class KotlinCrudController(
    val mongoCrudService: MongoCrudService,
    val mysqlCrudService: MySqlCrudService,
    val esCrudService: EsCrudService
) {
    @Value("\${app.upload.local.path:}")
    var UPLOAD_LOCAL_PATH: String = ""


    /**
     * 获取数据源！ 手动构建 HikariDataSource 导致数据库连接不释放！
     */
    fun getDataSource(db: DbConnection): DataSourceScope {

        var jdbcUrl = db.getJdbcUri();

        var config = HikariConfig()
        config.jdbcUrl = jdbcUrl;
        config.username = db.userName
        config.password = db.password
        config.maximumPoolSize = 1;

        var ds = HikariDataSource(config);
        return DataSourceScope(ds)
    }


    /**
     * 获取表定义，包括列
     */
    @PostMapping("/db-entity/tables/{id}")
    fun dbEntityList(id: String): ListResult<EntityDbItemData> {
        var db =
            mor.dev.dbConnection.queryById(id).toEntity().must().elseThrow { "找不到数据库连接" };

        var dbSource = getDataSource(db);
        var ret = usingScope(dbSource) {
            var ret = SqlDbEntityGenerator.db2Entity()
                .getTablesData()

            dbSource.value.connection.close();
            return@usingScope ret
        }

        return ListResult.of(ret);
    }


    @PostMapping("/db-entity/indexes/{id}")
    fun indexes(id: String): ListResult<TableIndexMetaData> {
        var db =
            mor.dev.dbConnection.queryById(id).toEntity().must().elseThrow { "找不到数据库连接" };

        var dbSource = getDataSource(db);
        var ret = usingScope(dbSource) {
            var ret = SqlDbEntityGenerator.db2Entity()
                .getAllIndexes()

            dbSource.value.connection.close();
            return@usingScope ret
        }

        return ListResult.of(ret);
    }

    @GetMapping("/db-entity/down/{id}")
    fun dbEntityDown(
        id: String,
        @Require style: String,
        pkgName: String,
        tables: Set<String>,
        response: HttpServletResponse
    ) {
        var db =
            mor.dev.dbConnection.queryById(id).toEntity().must().elseThrow { "找不到数据库连接" };
        var list = listOf<NameCodeInfo>()

        var dbSource = getDataSource(db);
        usingScope(dbSource) {
            if (style == "MyOql") {
                list = getMyOqlStyleList(pkgName, db.name, tables)
            } else if (style == "JPA") {
                list = getJpaStyleList(pkgName, db.name, tables)
            }

            dbSource.value.connection.close();
            return@usingScope list;
        }

        var file = File(
            FileUtil.resolvePath(
                System.getProperty("java.io.tmpdir"),
                CodeUtil.getCode() + ".zip"
            )
        )
        file.parentFile.mkdirs()

        var zipFile = ZipUtil.beginCompress(file);
        list.forEach { idName ->
            var fileName = idName.name;
            var text = idName.code
            zipFile.addFile(text.byteInputStream(const.utf8), fileName);
        }

        response.setDownloadFileName(style + "-entity-code.zip")
        response.outputStream.write(file.readBytes())
        file.delete()
    }

    private fun getJpaStyleList(pkgName: String, db: String, tables: Set<String>): List<NameCodeInfo> {
        var list = SqlDbEntityGenerator
            .db2Entity()
            .whereTable { tables.contains(it) }
            .toJpaCode(pkgName)

        list.forEach {
            if (it.name.isEmpty()) {
                it.name = db + ".java"
            } else {
                it.name = it.name + ".java"
            }
        }
        return list;
    }

    private fun getMyOqlStyleList(pkgName: String, db: String, tables: Set<String>): List<NameCodeInfo> {

        var list = SqlDbEntityGenerator
            .db2Entity()
            .whereTable { tables.isEmpty() || tables.contains(it) }
            .toKotlinCode(pkgName)


        list.forEach {
            if (it.name.isEmpty()) {
                it.name = db + ".kt"
            } else {
                it.name = it.name + ".kt"
            }
        }

        return list;
    }

    @PostMapping("/crud/groups")
    fun getGroup(dbType: DatabaseEnum?): ListResult<String> {
        if (dbType == null) {
            return ListResult();
        }

        if (dbType == DatabaseEnum.Mongo) {
            return mongoCrudService.getGroups()
        } else if (dbType == DatabaseEnum.Rdb) {
            return mysqlCrudService.getGroups()
        } else if (dbType == DatabaseEnum.ElasticSearch) {
            return esCrudService.getGroups()
        }

        throw RuntimeException("不识别的类型:${dbType}")
    }


    /**
     * @param
     */
    private fun getEntityInfo(dbType: DatabaseEnum?, entity: String): IdNamePath {
        getGroup(dbType).data.find { group ->
            var item = getEntities(dbType, group).data.firstOrNull { it.id == entity }
            if (item != null) {
                return IdNamePath(item.id, item.name, group)
            }
            return@find false;
        }

        return IdNamePath()
    }

    @PostMapping("/crud/entities")
    fun getEntities(dbType: DatabaseEnum?, group: String): ListResult<IdName> {
        if (dbType == null) {
            return ListResult()
        }

        if (dbType == DatabaseEnum.Mongo) {
            return mongoCrudService.getEntities(group)
        } else if (dbType == DatabaseEnum.Rdb) {
            return mysqlCrudService.getEntities(group)
        } else if (dbType == DatabaseEnum.ElasticSearch) {
            return esCrudService.getEntities(group);
        }

        throw RuntimeException("不识别的类型:${dbType}")
    }


    public enum class CrudTypeEnum {
        mvc,
        list,
        card,
        ref,
        route,
        routeYaml
    }

    @GetMapping("/crud/down")
    fun gen_crud_down(
        @Require type: String,
        @Require dbType: DatabaseEnum,
        group: String,
        entity: String,
        pkg: String,
        request: HttpServletRequest,
        response: HttpServletResponse
    ) {
        var server_path = "java/nancal/mvc"
        var web_path = "src/view"

        var prefix = "";
        var readme = JsonMap();
        var list: MutableList<IdName> = mutableListOf<IdName>();

        if (type == "all") {
            prefix = "all-code"

            getGroup(dbType).data.forEach { group ->

                var groupDefine = JsonMap();
                getEntities(dbType, group).data.forEach { entity ->
                    groupDefine.put(entity.id, entity.name)

                    list.addAll(
                        gen_crud(dbType, group, entity.id, pkg, request)
                            .data!!.values
                            .map { it as IdName }.map {
                                it.id = prefix + "/" + it.id;
                                it
                            }.toList()
                    )
                }


                readme.put(StringUtil.getKebabCase(group), groupDefine)
            }

        } else if (type == "group") {
            if (group.isEmpty()) {
                response.parentAlert("找不到组名")
                return;
            }
            prefix = group + "-code";

            var groupDefine = JsonMap();
            getEntities(dbType, group).data.forEach { entity ->
                groupDefine.put(entity.id, entity.name)
                list.addAll(
                    gen_crud(dbType, group, entity.id, pkg, request)
                        .data!!.values
                        .map { it as IdName }.map {
                            it.id = prefix + "/" + it.id;
                            it
                        }.toList()
                )
            }
            readme.put(StringUtil.getKebabCase(group), groupDefine)
        } else if (type == "entity") {
            if (group.isEmpty()) {
                response.parentAlert("找不到组名")
                return;
            }
            if (entity.isEmpty()) {
                response.parentAlert("找不到实体名")
                return;
            }
            prefix = group + "-" + entity + "-code"

            var groupDefine = JsonMap();

            var entityInfo = getEntityInfo(dbType, entity);
            groupDefine.put(entityInfo.id, entityInfo.name)
            readme.put(StringUtil.getKebabCase(group), groupDefine)

            list.addAll(
                gen_crud(dbType, group, entity, pkg, request)
                    .data!!.values
                    .map { it as IdName }.map {
                        it.id = prefix + "/" + it.id;
                        it
                    }.toList()
            )
        }


        var file = File(
            FileUtil.resolvePath(
                System.getProperty("java.io.tmpdir"),
                CodeUtil.getCode() + ".zip"
            )
        );

        var zipFile = ZipUtil.beginCompress(file);
        list.forEach { idName ->
            var fileName = idName.id;
            var text = idName.name
            zipFile.addFile(text.byteInputStream(const.utf8), fileName);
        }

        usingScope(JsonStyleScopeEnum.PRETTY) {
            zipFile.addFile(readme.ToJson().byteInputStream(const.utf8), prefix + "/readme.md")
        }

        response.setDownloadFileName(prefix + ".zip")
        response.outputStream.write(file.readBytes())
        file.delete()
    }


    @PostMapping("/crud/gen")
    fun gen_crud(
        @Require dbType: DatabaseEnum,
        @Require group: String,
        @Require entity: String,
        @Require pkg: String,
        request: HttpServletRequest
    ): ApiResult<JsonMap> {
        var server_path = "java/nancal/mvc"
        var web_path = "src/view"

        var mongo_entity: BaseMetaData<out Any>;
        if (dbType == DatabaseEnum.Mongo) {
            mongo_entity = mongoCrudService.getEntity(entity)
        } else if (dbType == DatabaseEnum.Rdb) {
            mongo_entity = mysqlCrudService.getEntity(entity)
        } else if (dbType == DatabaseEnum.ElasticSearch) {
            mongo_entity = esCrudService.getEntity(entity)
        } else {
            throw RuntimeException("不识别的数据库类型:${dbType}")
        }

        if (mongo_entity == null) {
            return ApiResult.error("找不到${entity}实体")
        }

        var entity_upper = StringUtil.getBigCamelCase(mongo_entity.userDefineTableName)

        var jsonMap = JsonMap();
        if (dbType == DatabaseEnum.Mongo) {
            jsonMap[CrudTypeEnum.mvc.toString()] = IdName(
                "api/${server_path}/${StringUtil.getSmallCamelCase(group)}/${entity_upper}Controller.kt",
                CrudCodeGeneratorUtil.genMongoMvcCrud(group, pkg, mongo_entity)
            )
        } else if (dbType == DatabaseEnum.Rdb) {
            jsonMap[CrudTypeEnum.mvc.toString()] = IdName(
                "api/${server_path}/${StringUtil.getSmallCamelCase(group)}/${entity_upper}Controller.kt",
                CrudCodeGeneratorUtil.genMySqlMvcCrud(group, pkg, mongo_entity)
            )
        } else if (dbType == DatabaseEnum.ElasticSearch) {
            jsonMap[CrudTypeEnum.mvc.toString()] = IdName(
                "api/${server_path}/${StringUtil.getSmallCamelCase(group)}/${entity_upper}Controller.kt",
                CrudCodeGeneratorUtil.genEsMvcCrud(group, pkg, mongo_entity)
            )
        }


        jsonMap[CrudTypeEnum.list.toString()] = IdName(
            "web/${web_path}/${StringUtil.getKebabCase(group)}/${StringUtil.getKebabCase(entity)}/${
                StringUtil.getKebabCase(
                    entity
                )
            }-list.vue",
            CrudCodeGeneratorUtil.genVue3List(group, mongo_entity)
        )
        jsonMap[CrudTypeEnum.card.toString()] = IdName(
            "web/${web_path}/${StringUtil.getKebabCase(group)}/${StringUtil.getKebabCase(entity)}/${
                StringUtil.getKebabCase(
                    entity
                )
            }-card.vue",
            CrudCodeGeneratorUtil.genVue3Card(group, mongo_entity)
        )

        jsonMap[CrudTypeEnum.ref.toString()] = IdName(
            "web/${web_path}/${StringUtil.getKebabCase(group)}/${StringUtil.getKebabCase(entity)}/ref-${
                StringUtil.getKebabCase(
                    entity
                )
            }.vue",
            CrudCodeGeneratorUtil.genVue3Ref(group, mongo_entity)
        )

        if( dbType == DatabaseEnum.Mongo) {
            //添加子对象
            (mongo_entity as MongoBaseMetaCollection<*>).systemDefineEntityType.AllFields
                .forEach { field ->
                    var type = field.type;
                    if (type.IsSimpleType()) {
                        return@forEach
                    }

                    if (type is BaseIdEntity) {
                        return@forEach
                    }

                    if (type is Map<*, *>) {
                        return@forEach
                    }


                    if (type.IsCollectionType) {
                        var comType = type.componentType ?: field.GetFirstGenericType()
                        var key = "ref-table-" + StringUtil.getKebabCase(comType.simpleName)
                        if (jsonMap.contains(key)) {
                            return@forEach
                        }


                        var v = IdName(
                            "web/${web_path}/${StringUtil.getKebabCase(group)}/${
                                StringUtil.getKebabCase(
                                    entity
                                )
                            }/ref-table-${
                                StringUtil.getKebabCase(
                                    comType.simpleName
                                )
                            }.vue",
                            CrudCodeGeneratorUtil.genVue3RefInputTable(comType)
                        )
                        jsonMap.put(key, v)
                    } else {
                        var key = "ref-" + StringUtil.getKebabCase(type.simpleName);

                        if (jsonMap.containsKey(key)) {
                            return@forEach
                        }

                        var v = IdName(
                            "web/${web_path}/${StringUtil.getKebabCase(group)}/${
                                StringUtil.getKebabCase(
                                    entity
                                )
                            }/ref-${
                                StringUtil.getKebabCase(
                                    type.simpleName
                                )
                            }.vue",
                            CrudCodeGeneratorUtil.genVue3RefCard(type)
                        )
                        jsonMap.put(key, v)
                    }
                }
        }


        var groupDefine = JsonMap();

        var entityInfo = getEntityInfo(dbType, entity);
        groupDefine.put(entityInfo.id, entityInfo.name)
        var readme = JsonMap();
        readme.put(StringUtil.getKebabCase(group), groupDefine)

        var readmeJson = "";
        usingScope(JsonStyleScopeEnum.PRETTY) {
            readmeJson = readme.ToJson()
        }

        jsonMap[CrudTypeEnum.route.toString()] = IdName(
            "web/src/router/auto-router.js",
            readmeJson
        )

        jsonMap[CrudTypeEnum.routeYaml.toString()] = IdName(
            "web/src/router/router.yaml",
            readme.ToYaml()
        )

        //遍历枚举
        var enms = mongo_entity.systemDefineEntityType.findAllEnum()
            .map {
                Enum4JsGenerator.generateEnum(it as Class<out Enum<*>>)
            }.joinToString("\n");

        if (enms.HasValue) {
            jsonMap["enum"] = IdName("web/src/api/enums.js", enms)
        }

        return ApiResult.of(jsonMap)
    }


}
